agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v3 2/7] Row pattern recognition patch (parse/analysis).
813+ messages / 2 participants
[nested] [flat]

* [PATCH v3 2/7] Row pattern recognition patch (parse/analysis).
@ 2023-07-26 10:49  Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/parser/parse_agg.c    |   7 ++
 src/backend/parser/parse_clause.c | 190 +++++++++++++++++++++++++++++-
 src/backend/parser/parse_expr.c   |   4 +
 src/backend/parser/parse_func.c   |   3 +
 4 files changed, 203 insertions(+), 1 deletion(-)

diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 85cd47b7ae..aa7a1cee80 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -564,6 +564,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -953,6 +957,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 334b9b42bd..ea2decc579 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -100,7 +100,10 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
-
+static void  transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static void transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
 
 /*
  * transformFromClause -
@@ -2950,6 +2953,10 @@ transformWindowDefinitions(ParseState *pstate,
 											 rangeopfamily, rangeopcintype,
 											 &wc->endInRangeFunc,
 											 windef->endOffset);
+
+		/* Process Row Pattern Recognition related clauses */
+		transformRPR(pstate, wc, windef);
+
 		wc->runCondition = NIL;
 		wc->winref = winref;
 
@@ -3815,3 +3822,184 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * transformRPR
+ *		Process Row Pattern Recognition related clauses
+ */
+static void
+transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	/* Check Frame option. Frame must start at current row */
+
+	/*
+	 * Window definition exists?
+	 */
+	if (windef == NULL)
+		return;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("FRAME must start at current row when row patttern recognition is used")));
+
+	/* Transform AFTER MACH SKIP TO clause */
+	wc->rpSkipTo = windef->rpCommonSyntax->rpSkipTo;
+
+	/* Transform SEEK or INITIAL clause */
+	wc->initial = windef->rpCommonSyntax->initial;
+
+	/* Transform DEFINE clause into list of TargetEntry's */
+	wc->defineClause = transformDefineClause(pstate, wc, windef);
+
+	/* Check PATTERN clause and copy to patternClause */
+	transformPatternClause(pstate, wc, windef);
+
+	/* Transform MEASURE clause */
+	transformMeasureClause(pstate, wc, windef);
+}
+
+/*
+ * transformDefineClause Process DEFINE clause and transform ResTarget into
+ *		list of TargetEntry.
+ *
+ * XXX we only support column reference in row pattern definition search
+ * condition, e.g. "price". <row pattern definition variable name>.<column
+ * reference> is not supported, e.g. "A.price".
+ */
+static List *
+transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	ListCell	*lc;
+	ResTarget	*restarget, *r;
+	List		*restargets;
+
+
+	/*
+	 * If Row Definition Common Syntax exists, DEFINE clause must exist.
+	 * (the raw parser should have already checked it.)
+	 */
+	Assert(windef->rpCommonSyntax->rpDefs != NULL);
+
+	/*
+	 * Check for duplicate row pattern definition variables.  The standard
+	 * requires that no two row pattern definition variable names shall be
+	 * equivalent.
+	 */
+	restargets = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		char	*name;
+		ListCell	*l;
+
+		restarget = (ResTarget *)lfirst(lc);
+		name = restarget->name;
+
+		/*
+		 * Make sure that row pattern definition search condition is a boolean
+		 * expression.
+		 */
+		transformWhereClause(pstate, restarget->val,
+							 EXPR_KIND_RPR_DEFINE, "DEFINE");
+
+		foreach(l, restargets)
+		{
+			char		*n;
+
+			r = (ResTarget *) lfirst(l);
+			n = r->name;
+
+			if (!strcmp(n, name))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("row pattern definition variable name \"%s\" appears more than once in DEFINE clause",
+								name),
+						 parser_errposition(pstate, exprLocation((Node *)r))));
+		}
+		restargets = lappend(restargets, restarget);
+	}
+	list_free(restargets);
+
+	return transformTargetList(pstate, windef->rpCommonSyntax->rpDefs,
+							   EXPR_KIND_RPR_DEFINE);
+}
+
+/*
+ * transformPatternClause
+ *		Process PATTERN clause and return PATTERN clause in the raw parse tree
+ */
+static void
+transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	ListCell	*lc, *l;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	/*
+	 * Primary row pattern variable names in PATTERN clause must appear in
+	 * DEFINE clause as row pattern definition variable names.
+	 */
+	wc->patternVariable = NIL;
+	wc->patternRegexp = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpPatterns)
+	{
+		A_Expr	*a;
+		char	*name;
+		char	*regexp;
+		bool	found = false;
+
+		if (!IsA(lfirst(lc), A_Expr))
+			ereport(ERROR,
+					errmsg("node type is not A_Expr"));
+
+		a = (A_Expr *)lfirst(lc);
+		name = strVal(a->lexpr);
+
+		foreach(l, windef->rpCommonSyntax->rpDefs)
+		{
+			ResTarget	*restarget = (ResTarget *)lfirst(l);
+
+			if (!strcmp(restarget->name, name))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("primary row pattern variable name \"%s\" does not appear in DEFINE clause",
+							name),
+					 parser_errposition(pstate, exprLocation((Node *)a))));
+		wc->patternVariable = lappend(wc->patternVariable, makeString(pstrdup(name)));
+		regexp = strVal(lfirst(list_head(a->name)));
+		wc->patternRegexp = lappend(wc->patternRegexp, makeString(pstrdup(regexp)));
+	}
+}
+
+/*
+ * transformMeasureClause
+ *		Process MEASURE clause
+ */
+static List *
+transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	if (windef->rowPatternMeasures == NIL)
+		return NIL;
+
+	ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("%s","MEASURE clause is not supported yet"),
+			 parser_errposition(pstate, exprLocation((Node *)windef->rowPatternMeasures))));
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fed8e4d089..8921b7ae01 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -557,6 +557,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 
@@ -1770,6 +1771,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3149,6 +3151,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_RPR_DEFINE:
+			return "DEFINE";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index b3f0b6a137..2ff3699538 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2656,6 +2656,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
-- 
2.25.1


----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0003-Row-pattern-recognition-patch-planner.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v1 3/4] Add alias to be used as "unset" state.
@ 2025-08-30 19:34  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-08-30 19:34 UTC (permalink / raw)

Add `unset_alias` string parameter to trenary reloption definition. This will allow
user explicitly switch trenary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as trenary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 24662d277c8..a96abcb056c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_trenary trenaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TRENARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TRENARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TRENARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_trenary *
 init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-					trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption;
 
 	newoption = (relopt_trenary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TRENARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
-				   trenary default_val, LOCKMODE lockmode)
+			trenary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_trenary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, trenary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TRENARY:
 			{
 				bool b;
+				relopt_trenary *opt = (relopt_trenary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.trenary_val = TRENARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f1..bff76dd859e 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TRENARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TRENARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7b96c7f9a80..779a6078ccb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		trenary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TRENARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TRENARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TRENARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TRENARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cdb..527c19b6fdf 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	trenary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 1a6717ed213..5c8eb9e97f9 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_trenary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_trenary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, trenary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_trenary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								trenary default_val, int offset);
+								trenary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 08f93bde007..1a1e818bd93 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	trenary		vacuum_index_cleanup; /* controls index vacuuming */
 	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6e..e7482c0faab 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for trenary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment; filename="v1-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



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

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



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

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2a 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..552c89869f 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treated as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment; filename="v2a-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2a-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread

* [PATCH v2 3/4] Add alias to be used as "unset" state.
@ 2025-09-04 16:22  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 813+ messages in thread

From: Nikolay Shaplov @ 2025-09-04 16:22 UTC (permalink / raw)

Add `unset_alias` string parameter to ternary reloption definition. This will allow
user explicitly switch ternary option to "unset" state. Use this feature to
implement `vacuum_index_cleanup` and gist's `buffering` reloptions as ternary
reloptions
---
 src/backend/access/common/reloptions.c | 91 +++++++++++---------------
 src/backend/access/gist/gistbuild.c    |  4 +-
 src/backend/commands/vacuum.c          | 11 ++--
 src/include/access/gist_private.h      | 10 +--
 src/include/access/reloptions.h        |  7 +-
 src/include/utils/rel.h                | 10 +--
 src/test/regress/expected/gist.out     |  3 +-
 7 files changed, 54 insertions(+), 82 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index f543f11001..3637646641 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -170,6 +170,27 @@ static relopt_ternary ternaryRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
+		NULL,
+		TERNARY_UNSET
+	},
+	{
+		{
+			"vacuum_index_cleanup",
+			"Controls index vacuuming and index cleanup",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		"auto",
+		TERNARY_UNSET
+	},
+	{
+		{
+			"buffering",
+			"Enables buffering build for this GiST index",
+			RELOPT_KIND_GIST,
+			AccessExclusiveLock
+		},
+		"auto",
 		TERNARY_UNSET
 	},
 	/* list terminator */
@@ -489,30 +510,6 @@ static relopt_real realRelOpts[] =
 	{{NULL}}
 };
 
-/* values from StdRdOptIndexCleanup */
-static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
-{
-	{"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
-	{"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
-	{"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
-/* values from GistOptBufferingMode */
-static relopt_enum_elt_def gistBufferingOptValues[] =
-{
-	{"auto", GIST_OPTION_BUFFERING_AUTO},
-	{"on", GIST_OPTION_BUFFERING_ON},
-	{"off", GIST_OPTION_BUFFERING_OFF},
-	{(const char *) NULL}		/* list terminator */
-};
-
 /* values from ViewOptCheckOption */
 static relopt_enum_elt_def viewCheckOptValues[] =
 {
@@ -524,28 +521,6 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 
 static relopt_enum enumRelOpts[] =
 {
-	{
-		{
-			"vacuum_index_cleanup",
-			"Controls index vacuuming and index cleanup",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		StdRdOptIndexCleanupValues,
-		STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
-	{
-		{
-			"buffering",
-			"Enables buffering build for this GiST index",
-			RELOPT_KIND_GIST,
-			AccessExclusiveLock
-		},
-		gistBufferingOptValues,
-		GIST_OPTION_BUFFERING_AUTO,
-		gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
-	},
 	{
 		{
 			"check_option",
@@ -913,13 +888,14 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
  */
 static relopt_ternary *
 init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-					ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption;
 
 	newoption = (relopt_ternary *) allocate_reloption(kinds,
 									RELOPT_TYPE_TERNARY, name, desc, lockmode);
 	newoption->default_val = default_val;
+	newoption->unset_alias = unset_alias;
 
 	return newoption;
 }
@@ -930,10 +906,10 @@ init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
  */
 void
 add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
-				   ternary default_val, LOCKMODE lockmode)
+			ternary default_val, const char* unset_alias, LOCKMODE lockmode)
 {
 	relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
-												 default_val, lockmode);
+											default_val, unset_alias, lockmode);
 
 	add_reloption((relopt_gen *) newoption);
 }
@@ -947,11 +923,11 @@ add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
 void
 add_local_ternary_reloption(local_relopts *relopts, const char *name,
 							const char *desc, ternary default_val,
-							int offset)
+							const char* unset_alias, int offset)
 {
 	relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
 												name, desc,
-												default_val, 0);
+												default_val, unset_alias, 0);
 
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
@@ -1692,8 +1668,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 		case RELOPT_TYPE_TERNARY:
 			{
 				bool b;
+				relopt_ternary *opt = (relopt_ternary *) option->gen;
+
 				parsed = parse_bool(value, &b);
 				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+
+				if (!parsed && opt->unset_alias)
+				{
+					if (pg_strcasecmp(value, opt->unset_alias) == 0)
+					{
+						option->values.ternary_val = TERNARY_UNSET;
+						parsed = true;
+					}
+				}
 				if (validate && !parsed)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1988,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
 		offsetof(StdRdOptions, parallel_workers)},
-		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+		{"vacuum_index_cleanup", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
 		{"vacuum_truncate", RELOPT_TYPE_TERNARY,
 		offsetof(StdRdOptions, vacuum_truncate)},
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9b2ec9815f..7f641f7825 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -213,9 +213,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 */
 	if (options)
 	{
-		if (options->buffering_mode == GIST_OPTION_BUFFERING_ON)
+		if (options->buffering_mode == TERNARY_TRUE)
 			buildstate.buildMode = GIST_BUFFERING_STATS;
-		else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF)
+		else if (options->buffering_mode == TERNARY_FALSE)
 			buildstate.buildMode = GIST_BUFFERING_DISABLED;
 		else					/* must be "auto" */
 			buildstate.buildMode = GIST_BUFFERING_AUTO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 977babff54..61b4bdb682 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2175,22 +2175,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
-		StdRdOptIndexCleanup vacuum_index_cleanup;
+		ternary vacuum_index_cleanup;
 
 		if (rel->rd_options == NULL)
-			vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+			vacuum_index_cleanup = TERNARY_UNSET;
 		else
 			vacuum_index_cleanup =
 				((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
 
-		if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
+		if (vacuum_index_cleanup == TERNARY_UNSET)
 			params.index_cleanup = VACOPTVALUE_AUTO;
-		else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
+		else if (vacuum_index_cleanup == TERNARY_TRUE)
 			params.index_cleanup = VACOPTVALUE_ENABLED;
 		else
 		{
-			Assert(vacuum_index_cleanup ==
-				   STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
+			Assert(vacuum_index_cleanup == TERNARY_FALSE);
 			params.index_cleanup = VACOPTVALUE_DISABLED;
 		}
 	}
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 39404ec7cd..a931c988fb 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -380,14 +380,6 @@ typedef struct GISTBuildBuffers
 	int			rootlevel;
 } GISTBuildBuffers;
 
-/* GiSTOptions->buffering_mode values */
-typedef enum GistOptBufferingMode
-{
-	GIST_OPTION_BUFFERING_AUTO,
-	GIST_OPTION_BUFFERING_ON,
-	GIST_OPTION_BUFFERING_OFF,
-} GistOptBufferingMode;
-
 /*
  * Storage type for GiST's reloptions
  */
@@ -395,7 +387,7 @@ typedef struct GiSTOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
-	GistOptBufferingMode buffering_mode;	/* buffering build mode */
+	ternary		buffering_mode;	/* buffering build mode */
 } GiSTOptions;
 
 /* gist.c */
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a436697658..d2a1c7afb7 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -99,6 +99,7 @@ typedef struct relopt_bool
 typedef struct relopt_rernary
 {
 	relopt_gen	gen;
+	const char	*unset_alias; /* word that will be treaed as unset value */
 	int 		default_val;
 } relopt_ternary;
 
@@ -191,7 +192,8 @@ extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
 extern void add_ternary_reloption(bits32 kinds, const char *name,
-					const char *desc, int default_val, LOCKMODE lockmode);
+					const char *desc, ternary default_val,
+					const char* unset_alias, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -213,7 +215,8 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 int offset);
 extern void add_local_ternary_reloption(local_relopts *relopts,
 								const char *name, const char *desc,
-								ternary default_val, int offset);
+								ternary default_val, const char* unset_alias,
+								int offset);
 extern void add_local_int_reloption(local_relopts *relopts, const char *name,
 									const char *desc, int default_val,
 									int min_val, int max_val, int offset);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 7346d618f9..95a18c6d16 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -329,14 +329,6 @@ typedef struct AutoVacOpts
 	float8		analyze_scale_factor;
 } AutoVacOpts;
 
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
 typedef struct StdRdOptions
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
@@ -345,7 +337,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
-	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
+	ternary		vacuum_index_cleanup; /* controls index vacuuming */
 	ternary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..76751d1859 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -12,8 +12,7 @@ create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = au
 drop index gist_pointidx2, gist_pointidx3, gist_pointidx4;
 -- Make sure bad values are refused
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value);
-ERROR:  invalid value for enum option "buffering": invalid_value
-DETAIL:  Valid values are "on", "off", and "auto".
+ERROR:  invalid value for ternary option "buffering": invalid_value
 create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9);
 ERROR:  value 9 out of bounds for option "fillfactor"
 DETAIL:  Valid values are between "10" and "100".
-- 
2.39.2


--nextPart5129932.5fSG56mABF
Content-Disposition: attachment; filename="v2-0004-Extra-tests.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v2-0004-Extra-tests.patch"



^ permalink  raw  reply  [nested|flat] 813+ messages in thread


end of thread, other threads:[~2025-09-04 16:22 UTC | newest]

Thread overview: 813+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-07-26 10:49 [PATCH v3 2/7] Row pattern recognition patch (parse/analysis). Tatsuo Ishii <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-08-30 19:34 [PATCH v1 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2 3/4] Add alias to be used as "unset" state. Nikolay Shaplov <[email protected]>
2025-09-04 16:22 [PATCH v2a 3/4] Add alias to be used as "unset" state. 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